<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// | PHP Version 4                                                        |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2002 The PHP Group                                |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.0 of the PHP license,       |
// | that is bundled with this package in the file LICENSE, and is        |
// | available at through the world-wide-web at                           |
// | http://www.php.net/license/2_02.txt.                                 |
// | If you did not receive a copy of the PHP license and are unable to   |
// | obtain it through the world-wide-web, please send a note to          |
// | license@php.net so we can mail you a copy immediately.               |
// +----------------------------------------------------------------------+
// | Authors:   Carsten Lucke <luckec@tool-garage.de>                     |
// +----------------------------------------------------------------------+
//
//    $Id: Composite.php,v 1.8 2006/04/01 17:15:31 schst Exp $

/**
 * driver not found
 *
 * @access  public
 */
define('DATE_HOLIDAYS_DRIVER_NOT_FOUND', 100);

/**
 * Composite driver - you can use this one to combine two or more drivers
 *
 * @category    Date
 * @package     Date_Holidays
 * @subpackage  Driver
 * @version     $Id: Composite.php,v 1.8 2006/04/01 17:15:31 schst Exp $
 * @author      Carsten Lucke <luckec@tool-garage.de>
 */
class Date_Holidays_Driver_Composite extends Date_Holidays_Driver
{
   /**
    * compound of drivers
    *
    * @access   private
    * @var      array
    */
    var $_drivers = array();

   /**
    * Driver-ids ordered by importance
    *
    * @access   private
    * @var      array
    */
    var $_driverIds = array();

   /**
    * Constructor
    *
    * Use the Date_Holidays::factory() method to construct an object of a certain driver
    *
    * @access   protected
    */
    function Date_Holidays_Driver_Composite()
    {
    }

   /**
    * Build the internal arrays that contain data about the calculated holidays
    *
    * @access   private
    * @return   boolean true on success, otherwise a PEAR_ErrorStack object
    * @throws   object PEAR_ErrorStack
    */
    function _buildHolidays()
    {
    }

   /**
    * Add a driver component
    *
    * @access   public
    * @param    object Date_Holidays_Driver $driver driver-object
    * @return   boolean true on success, false otherwise
    */
    function addDriver($driver)
    {
        if (! is_a($driver, 'Date_Holidays_Driver')) {
            return false;
        }

        $id                     = md5(serialize($driver));
        $this->_drivers[$id]    = $driver;
        array_push($this->_driverIds, $id);

        $this->_internalNames   = array_merge(
                $driver->getInternalHolidayNames(), $this->_internalNames);
        return true;
    }

   /**
    * Remove a driver component
    *
    * @access   public
    * @param    object Date_Holidays_Driver $driver driver-object
    * @return   boolean true on success, otherwise a PEAR_Error object
    * @throws   object PEAR_Error   DATE_HOLIDAYS_DRIVER_NOT_FOUND
    */
    function removeDriver($driver)
    {
        if (! is_a($driver, 'Date_Holidays_Driver')) {
            return false;
        }

        $id = md5(serialize($driver));
        // unset driver object
        if (! isset($this->_drivers[$id])) {
            return Date_Holidays::raiseError(DATE_HOLIDAYS_DRIVER_NOT_FOUND, 'Driver not found');
        }
        unset($this->_drivers[$id]);

        // unset driver's prio
        $index = array_search($id, $this->_driverIds);
        unset($this->_driverIds[$index]);

        // rebuild the internal-names array
        $this->_internalNames = array();
        foreach ($this->_driverIds as $id) {
            $this->_internalNames =
                    array_merge($this->_drivers[$id]->_internalNames, $this->_internalNames);
        }

        return true;
    }

   /**
    * Returns the specified holiday
    *
    * Return format:
    * <pre>
    *   array(
    *       'title' =>  'Easter Sunday'
    *       'date'  =>  '2004-04-11'
    *   )
    * </pre>
    *
    * @access   public
    * @param    string  $internalName   internal name of the holiday
    * @param    string  $locale         locale setting that shall be used by this method
    * @return   object Date_Holidays_Holiday    holiday's information on success, otherwise a PEAR_Error object
    * @throws   object PEAR_Error       DATE_HOLIDAYS_INVALID_INTERNAL_NAME
    */
    function getHoliday($internalName, $locale = null)
    {
        foreach ($this->_driverIds as $id) {
            $holiday = $this->_drivers[$id]->getHoliday($internalName, $locale);
            if (Date_Holidays::isError($holiday)) {
               /**
                * lets skip this error, perhaps another driver knows this internal-name
                */
                continue;
            }
            return $holiday;
        }

        return Date_Holidays::raiseError(DATE_HOLIDAYS_INVALID_INTERNAL_NAME, 'Invalid internal name: ' . $internalName);
    }

   /**
    * Returns date of a holiday
    *
    * @access   public
    * @param    string  $internalName   internal name for holiday
    * @return   object Date             date of the holiday as PEAR::Date object on success, otherwise a PEAR_Error object
    * @throws   object PEAR_Error       DATE_HOLIDAYS_INVALID_INTERNAL_NAME
    */
    function getHolidayDate($internalName)
    {
        foreach ($this->_driverIds as $id) {
            $date = $this->_drivers[$id]->getHolidayDate($internalName);
            if (Date_Holidays::isError($date)) {
               /**
                * lets skip this error, perhaps another driver knows this internal-name
                */
                continue;
            }
            return $date;
        }

        return Date_Holidays::raiseError(DATE_HOLIDAYS_INVALID_INTERNAL_NAME, 'Invalid internal name: ' . $internalName);
    }

   /**
    * Returns dates of all holidays or those accepted by the specified filter.
    *
    * @access   public
    * @param    Date_Holidays_Filter    filter-object (or an array !DEPRECATED!)
    * @return   array   array with holidays' dates on success, otherwise a PEAR_ErrorStack object
    * @throws   object PEAR_ErrorStack   DATE_HOLIDAYS_INVALID_INTERNAL_NAME
    */
    function getHolidayDates($filter = null)
    {
        if (is_null($filter)) {
            $filter = new Date_Holidays_Filter_Blacklist(array());
        } elseif (is_array($filter)) {
            $filter = new Date_Holidays_Filter_Whitelist($filter);
        }

        $errorStack = &Date_Holidays::getErrorStack();
        $dates      = array();
        $notFound   = array();

        foreach ($this->_internalNames as $internalName) {
            // check if the filter permits further processing
            if (! $filter->accept($internalName)) {
                continue;
            }

            foreach ($this->_driverIds as $id) {
                $date = $this->_drivers[$id]->getHolidayDate($internalName);
                if (Date_Holidays::isError($date)) {
                    if ($date->getCode() == DATE_HOLIDAYS_DATE_UNAVAILABLE) {
                       /**
                        * this means a fatal error (would be the right place for sth. like an assert,
                        * as this should normally never happen)
                        */
                        $errorStack->push(DATE_HOLIDAYS_DATE_UNAVAILABLE, 'error', array(),
                            'No date found for holiday with internal name: ' . $internalName, false, debug_backtrace());
                        continue;
                    }

                   /**
                    * current driver doesn't have this internalName, trying next driver
                    */
                    array_push($notFound, $internalName);
                    continue;
                }
               /**
                * internal name found in highest priorized driver, stepping to next internal name
                * checks if internal name is existent in $notFound array and unsets this entry as it has been found now
                */
                $notFound = array_unique($notFound);
                if (in_array($internalName, $notFound)) {
                    unset($notFound[array_search($internalName, $notFound)]);
                }
                $dates[$internalName]   = $date;
                continue 2;
            }
        }

        if (! empty($notFound)) {
            foreach ($notFound as $internalName) {
                $errorStack->push(DATE_HOLIDAYS_INVALID_INTERNAL_NAME, 'error', array(),
                    'Invalid internal name: ' . $internalName, false, debug_backtrace());
            }
        }

        if ($errorStack->hasErrors() && ! empty($notFound)) {
            return $errorStack;
        }
        return $dates;
    }

   /**
    * Returns the title of the holiday, if any was found, matching the specified date.
    *
    * Normally the method will return the title/data for the first holiday matching the date.
    * If you want the mthod to continue searching holidays for the specified date, set the 4th param to true
    * If multiple holidays match your date, the return value will be an array of the titles/data.
    * <pre>
    * array(
    *   array(
    *       'title' => 'New Year',
    *       'date'  => Object of type Date
    *   ),
    *   array(
    *       'title' => 'Circumcision of Jesus',
    *       'date'  => Object of type Date
    *   )
    * )
    * </pre>
    *
    * @access   public
    * @param    mixed   $date       date (timestamp | string | PEAR::Date object)
    * @param    string  $locale     locale setting that shall be used by this method
    * @param    boolean $multiple
    * @return   object  object of type Date_Holidays_Holiday on success (numeric array of those on multiple search); if no holiday was found, matching this date, null is returned
    * @uses     getHoliday()
    * @uses     getHolidayTitle()
    * @see      getHoliday()
    **/
    function getHolidayForDate($date, $locale = null, $multiple = false)
    {
        $holidays = array();
        foreach ($this->_driverIds as $id) {
            $holiday = $this->_drivers[$id]->getHolidayForDate($date, $locale, $multiple);
            if (is_null($holiday)) {
               /**
                * No holiday found for this date in the current driver, trying next one
                */
                continue;
            }

            if (is_array($holiday)) {
                for ($i = 0; $i < count($holiday); ++$i) {
                    $holidays[] = $holiday[$i];
                }
            } else {
                $holidays[]     = $holiday;
            }

            if (! $multiple) {
                return $holiday;
            }
        }

        if (empty($holidays)) {
            return null;
        }
        return $holidays;
    }

   /**
    * Returns all holidays that were found
    *
    * Return format:
    * <pre>
    *   array(
    *       'easter' =>  array(
    *           'title' =>  'Easter Sunday'
    *           'date'  =>  '2004-04-11'
    *       ),
    *       'eastermonday'  =>  array(
    *           'title' =>  'Easter Monday'
    *           'date'  =>  '2004-04-12'
    *       ),
    *       ...
    *   )
    * </pre>
    *
    * @access   public
    * @param    Date_Holidays_Filter    filter-object (or an array !DEPRECATED!)
    * @return   array   numeric array containing objects of Date_Holidays_Holiday on success, otherwise a PEAR_ErrorStack object
    * @throws   object PEAR_ErrorStack   DATE_HOLIDAYS_INVALID_INTERNAL_NAME
    */
    function getHolidays($filter = null)
    {
        if (is_null($filter)) {
            $filter = new Date_Holidays_Filter_Blacklist(array());
        } elseif (is_array($filter)) {
            $filter = new Date_Holidays_Filter_Whitelist($filter);
        }

        $errorStack = &Date_Holidays::getErrorStack();
        $holidays   = array();
        $notFound   = array();

        foreach ($this->_internalNames as $internalName) {
            // check if the filter permits further processing
            if (! $filter->accept($internalName)) {
                continue;
            }

            foreach ($this->_driverIds as $id) {
                $holiday = $this->_drivers[$id]->getHoliday($internalName);
                if (Date_Holidays::isError($holiday)) {
                   /**
                    * current driver doesn't have this internalName, trying next driver
                    */
                    array_push($notFound, $internalName);
                    continue;
                }
               /**
                * internal name found in highest priorized driver, stepping to next internal name
                * checks if internal name is existent in $notFound array and unsets this entry as it has been found now
                */
                $notFound = array_unique($notFound);
                if (in_array($internalName, $notFound)) {
                    unset($notFound[array_search($internalName, $notFound)]);
                }
                $holidays[$internalName] = $holiday;
                continue 2;
            }
        }

        if (! empty($notFound)) {
            foreach ($notFound as $internalName) {
                $errorStack->push(DATE_HOLIDAYS_INVALID_INTERNAL_NAME, 'error', array(),
                    'Invalid internal name: ' . $internalName, false, debug_backtrace());
            }
        }

        if ($errorStack->hasErrors() && ! empty($notFound)) {
            return $errorStack;
        }
        return $holidays;
    }

   /**
    * Returns localized title for a holiday
    *
    * @access   public
    * @param    string  $internalName   internal name for holiday
    * @param    string  $locale         locale setting that shall be used by this method
    * @return   string  title on success, otherwise a PEAR_Error object
    * @throws   object PEAR_Error   DATE_HOLIDAYS_INVALID_INTERNAL_NAME, DATE_HOLIDAYS_TITLE_UNAVAILABLE
    */
    function getHolidayTitle($internalName, $locale = null)
    {
        foreach ($this->_driverIds as $id) {
            $title = $this->_drivers[$id]->getHolidayTitle($internalName, $locale);
            if (Date_Holidays::isError($title)) {
               /**
                * lets skip this error, perhaps another driver knows this internal-name
                */
                continue;
            }
            return $title;
        }

        return Date_Holidays::raiseError(DATE_HOLIDAYS_INVALID_INTERNAL_NAME, 'Invalid internal name: ' . $internalName);
    }

   /**
    * Returns localized titles of all holidays or those specififed in $restrict array
    *
    * @access   public
    * @param    Date_Holidays_Filter    filter-object (or an array !DEPRECATED!)
    * @param    string  $locale     locale setting that shall be used by this method
    * @return   array   array with localized holiday titles on success, otherwise a PEAR_Error object
    * @throws   object PEAR_Error   DATE_HOLIDAYS_INVALID_INTERNAL_NAME
    */
    function getHolidayTitles($filter = null, $locale = null)
    {
        if (is_null($filter)) {
            $filter = new Date_Holidays_Filter_Blacklist(array());
        } elseif (is_array($filter)) {
            $filter = new Date_Holidays_Filter_Whitelist($filter);
        }

        $errorStack = &Date_Holidays::getErrorStack();
        $titles     = array();
        $notFound   = array();

        foreach ($this->_internalNames as $internalName) {
            // check if the filter permits further processing
            if (! $filter->accept($internalName)) {
                continue;
            }

            foreach ($this->_driverIds as $id) {
                $title = $this->_drivers[$id]->getHolidayTitle($internalName, $locale);
                if (Date_Holidays::isError($title)) {
                   /**
                    * current driver doesn't have this internalName, trying next driver
                    */
                    array_push($notFound, $internalName);
                    continue;
                }
               /**
                * internal name found in highest priorized driver, stepping to next internal name
                * checks if internal name is existent in $notFound array and unsets this entry as it has been found now
                */
                $notFound = array_unique($notFound);
                if (in_array($internalName, $notFound)) {
                    unset($notFound[array_search($internalName, $notFound)]);
                }
                $titles[$internalName]   = $title;
                continue 2;
            }
        }

        if (! empty($notFound)) {
            foreach ($notFound as $internalName) {
                $errorStack->push(DATE_HOLIDAYS_INVALID_INTERNAL_NAME, 'error', array(),
                    'Invalid internal name: ' . $internalName, false, debug_backtrace());
            }
        }

        if ($errorStack->hasErrors() && ! empty($notFound)) {
            return $errorStack;
        }
        return $titles;
    }

   /**
    * Using this method doesn't affect anything. If you have been able to add your driver to this compound,
    * you should also be able to directly execute this action.
    * This method is only available to keep abstraction working.
    *
    * @access   public
    */
    function getYear()
    {
    }

   /**
    * This (re)sets the year of every driver-object in the compound.
    *
    * Note that this will cause every attached driver to recalculate the holidays!
    *
    * @access   public
    * @param    int     year
    * @return   boolean true on success, otherwise a PEAR_ErrorStack object
    * @throws   object PEAR_ErrorStack
    */
    function setYear($year)
    {
        $errors = false;

        foreach ($this->_driverIds as $id) {
            if ($this->_drivers[$id]->setYear($year) != true) {
                $errors = true;
            }
        }

        if ($errors) {
            return Date_Holidays::getErrorStack();
        }
        return true;
    }

   /**
    * Determines whether a date represents a holiday or not.
    *
    * The method searches all added drivers for this date, to determine whether it's a holiday.
    *
    * @access   public
    * @param    mixed   $date   date (can be a timestamp, string or PEAR::Date object)
    * @param    Date_Holidays_Filter $filter    filter-object (or an array !DEPRECATED!)
    * @return   boolean true if date represents a holiday, otherwise false
    */
    function isHoliday($date, $filter = null)
    {
        foreach ($this->_driverIds as $id) {
            if ($this->_drivers[$id]->isHoliday($date, $filter)) {
                return true;
            }
            continue;
        }

        return false;
    }

   /**
    * Using this method doesn't affect anything. If you have bben able to add your driver to this compound,
    * you should also be able to directly execute this action.
    * This method is only available to keep abstraction working.
    *
    * @access   public
    */
    function setLocale($locale)
    {
    }
}
?>
